/**
 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */



function main(): int {
    let failures: int = 0;


    failures += check(createDefault(),"Create default empty {{.item.objectType}}")

    failures += check(createTAFromEmptyArrayBuffer(),"Create default empty {{.item.objectType}} from empty source");
    failures += check(createTAFromEmptyArrayBufferOneParamNoOffset(),"Create default empty {{.item.objectType}} from empty source one param");
    failures += check(createTAFromEmptyArrayBufferTwoParams(),"Create default empty {{.item.objectType}} from empty source two params");

    failures += check(createTAFromNonEmptyArrayBuffer(),"Create default empty {{.item.objectType}} from non empty source");
    failures += check(createTAFromNonEmptyArrayBufferOneParamNoOffset(),"Create default empty {{.item.objectType}} from non empty source one param");
    failures += check(createTAFromNonEmptyArrayBufferTwoParams(),"Create default empty {{.item.objectType}} from non empty source two params");

    failures += check(createTAFromNonEmptyArrayBufferOneParamWithOffset(),"Create default empty {{.item.objectType}} from non empty source with offset");
    failures += check(createTAFromNonEmptyArrayBufferOneParamWithOffsetAndSize(),"Create default empty {{.item.objectType}} from non empty source with offset and size");


    failures += check(createArrayFromArray(), "Create Array object from source array");
    failures += check(createArrayFromArrayWithOffset(), "Create Array object from source array with given offset");

    failures += check(createFilteredArrayFromGiven(), "Create Array object from source array with applied filter");


    failures += check(testLastIndexOfNotFound(), "Try to find missed item");
    failures += check(testLastIndexOf4(), "Try to find last item at 4");
    failures += check(testLastIndexOf3(), "Try to find last item at 3");


    failures += check(testMap(), "Try to Array map function");


    failures += check(testSlice0(), "Try to Array slice(0) function");
    failures += check(testSlice1(), "Try to Array slice(1) function");
    failures += check(testSlice2(), "Try to Array slice(2) function");
    failures += check(createArray_Set_And_Get(), "Create Array from source data");


    {% for ci in copyWithin -%}
    failures += check({{.ci.name}}(), "Test262: {{.ci.name}}");
    {%+ endfor %}


    if (failures > 0) return 1
    return 0;
}

function check(result: int, message: String): int {
    if(result == 0) {
      return 0;
    }
    console.println("\nFAILED: " + message);
    return 1;
}

function createDefault(): int {
    let target: {{.item.objectType}} = new {{.item.objectType}}();
    if(target.length == 0 && target.byteOffset == 0) return 0;
    return 1;
}

function createTAFromEmptyArrayBuffer(): int {
    let ss = new ArrayBuffer(0);  // Buffer 0-bytes length;
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss);
    } catch(e) {
        return 1;
    }

    if(target.length == 0 && target.byteOffset == 0) return 0;
    return 1;
}

function createTAFromNonEmptyArrayBuffer(): int {
    let ss = new ArrayBuffer(5*{{.item.primitiveSizeBytes}});  // Buffer 5-items length;
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss);
    } catch(e) {
        return 1;
    }

    if(target.byteLength == 5*{{.item.primitiveSizeBytes}} && target.byteOffset == 0) return 0;
    console.println("Error: Actual bytes length: " + target.byteLength);
    return 1;
}


function createTAFromEmptyArrayBufferOneParamNoOffset(): int {
    let ss = new ArrayBuffer(0);
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss, 0);
    } catch(e) {
        return 1;
    }

    if(target.length == 0 && target.byteOffset == 0) return 0;
    return 1;
}

function createTAFromNonEmptyArrayBufferOneParamNoOffset(): int {
    let ss = new ArrayBuffer(5*{{.item.primitiveSizeBytes}});  // Buffer 5-items length;
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss, 0);
    } catch(e) {
        return 1;
    }

    if(target.byteLength == 5*{{.item.primitiveSizeBytes}} && target.byteOffset == 0) return 0;
    console.println("Error: Actual bytes length: " + target.byteLength);
    return 1;
}

function createTAFromNonEmptyArrayBufferOneParamWithOffset(): int {
    let ss = new ArrayBuffer(7*{{.item.primitiveSizeBytes}});  // Buffer 7-items length;
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss, 2*{{.item.primitiveSizeBytes}});
    } catch(e) {
        console.println(e);
        return 1;
    }

    if(target.byteLength == 5*{{.item.primitiveSizeBytes}} && target.byteOffset == 2*{{.item.primitiveSizeBytes}}) return 0;
    console.println("Error: Actual bytes length: " + target.byteLength);
    console.println("Error: Actual bytes offset: " + target.byteOffset);
    return 1;
}

function createTAFromNonEmptyArrayBufferOneParamWithOffsetAndSize(): int {
    let ss = new ArrayBuffer(7*{{.item.primitiveSizeBytes}});  // Buffer 7-items length;
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss, 2*{{.item.primitiveSizeBytes}}, 4);
    } catch(e) {
        console.println(e);
        return 1;
    }

    if(target.byteLength == 5*{{.item.primitiveSizeBytes}} && target.byteOffset == 2*{{.item.primitiveSizeBytes}} && target.length == 4) return 0;
    console.println("Error: Actual bytes length: " + target.byteLength);
    console.println("Error: Actual bytes offset: " + target.byteOffset);
    return 1;
}



function createTAFromEmptyArrayBufferTwoParams(): int {
    let ss = new ArrayBuffer(0);
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss, 0, 0);
    } catch(e) {
        return 1;
    }

    if(target.length == 0 && target.byteOffset == 0) return 0;
    return 1;
}

function createTAFromNonEmptyArrayBufferTwoParams(): int {
    let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});  // Buffer 5-items length;
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss, 0, 0);
    } catch(e) {
        return 1;
    }

    if(target.byteLength == 5*{{.item.primitiveSizeBytes}} && target.byteOffset == 0) return 0;
    console.println("Error: Actual bytes length: " + target.byteLength);
    return 1;
}

const source: {{.item.primitiveType}}[] = {{.item.data}};

function createArray_Set_And_Get(): int {
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss);
    } catch(e) {
        return 1;
    }
    for(let i:int = 0; i < source.length; i++) {
        try {
            target.set(i, source[i] as {{.item.primitiveType}});
        } catch(e) {
            console.print("Catched exception at insert");
            return 1;
        }

    }
    //console.println("Validate....");
    let acc: int = 0;

    for(let i:int = 0; i < source.length; i++) {
        let back: {{.item.primitiveType}} = target.at(i);

        if(back != source[i]) {
            console.println("Data mismatch for {{.item.objectType}} " + " expected: " + source[i] + " at " + i + " but actual: " + back);
            acc++;
        }
    }
    return acc;
}

function createArrayFromArray(): int {
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss);
    } catch(e) {
        return 1;
    }

    try {
        target.set(source);
    } catch(e) {
        return 1;
    }

    //console.println("Validate....");
    let acc: int = 0;

    let sumExp: {{.item.primitiveType}} = 0;
    let sumAct: {{.item.primitiveType}} = 0;
    for (let i: int = 0; i < source.length; i++) {
        sumExp += source[i]
        sumAct += target.at(i)
    }

    if (sumAct != sumExp) {
        console.println("Sum mismatch {{.item.objectType}} " + " expected: " + sumExp + " but actual: " + sumAct);
        acc++;
    }

    for(let i:int = 0; i < source.length; i++) {
        let back: {{.item.primitiveType}} = target.at(i);

        if(back != source[i]) {
            console.println("Data mismatch for {{.item.objectType}} " + " expected: " + source[i] + " at " + i + " but actual: " + back);
            acc++;
        }
    }

    return acc;
}


function createArrayFromArrayWithOffset(): int {
    let offset: int = 3;
    let ss = new ArrayBuffer((source.length + offset) * {{.item.primitiveSizeBytes}});
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss);
    } catch(e) {
        return 1;
    }
    try {
        target.set(source, offset);
    } catch(e) {
        console.println("can't set")
        return 1;
    }

    //console.println("Validate....");
    let acc: int = 0;

    for(let i:int = offset; i < source.length; i++) {
        let back: {{.item.primitiveType}} = target.at(i);

        if(back != source[i-offset]) {
            console.println("Data mismatch for {{.item.objectType}} " + " expected: " + source[i] + " at " + i + " but actual: " + back);
            acc++;
        }
    }
    return acc;
}

function createFilteredArrayFromGiven(): int {
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});
    let origin: {{.item.objectType}};
    try {
        origin = new {{.item.objectType}}(ss);
        origin.set(source);
    } catch(e) {
        return 1;
    }

    let target: {{.item.objectType}};
    try {
        target = origin.filter((val: {{.item.primitiveType}}, index: int): boolean => { return val > 0});
    } catch(e) {
        return 1;
    }

    let targetIdx = 0
    for (let originIdx = 0; originIdx < origin.length; originIdx++) {
        if (origin.at(originIdx) <= 0) {
            continue
        }
        if (targetIdx >= target.length) {
            console.println("filter: buffer overflow")
            return 1
        }
        if (target.at(targetIdx) != origin.at(originIdx)) {
            console.println("wrong filtered item " + target.at(targetIdx))
            return 1
        }
        targetIdx++
    }
    if (targetIdx != target.length) {
        console.println("new element " + target.at(targetIdx))
        return 1
    }
    return 0;
}


function testLastIndexOfNotFound(): int {

    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});
    let target: {{.item.objectType}};
    try {
        target = new {{.item.objectType}}(ss);
        target.set(source);
    } catch(e) {
        return 1;
    }

    let nf: int = target.lastIndexOf(11 as {{.item.primitiveType}});

    if(nf == -1) return 0;
    return 1;


}

function testLastIndexOf4(): int {
    let source: {{.item.primitiveType}}[] = [10 as {{.item.primitiveType}}, 20 as {{.item.primitiveType}}, 50 as {{.item.primitiveType}},
                                            50 as {{.item.primitiveType}}, 50 as {{.item.primitiveType}}, 60 as {{.item.primitiveType}}];
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});

    let target: {{.item.objectType}};

    try {
        target = new {{.item.objectType}}(ss);
        target.set(source);
    } catch(e) {
        console.println(e);
        return 1;
    }

    let result: int = target.lastIndexOf(50 as {{.item.primitiveType}}, 5);
    if(result == 4) return 0;

    return 1;
}

function testLastIndexOf3(): int {
    let source: {{.item.primitiveType}}[] = [10 as {{.item.primitiveType}}, 20 as {{.item.primitiveType}}, 50 as {{.item.primitiveType}},
                                            50 as {{.item.primitiveType}}, 50 as {{.item.primitiveType}}, 60 as {{.item.primitiveType}}];
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});

    let target: {{.item.objectType}};

    try {
        target = new {{.item.objectType}}(ss);
        target.set(source);
    } catch(e) {
        console.println(e);
        return 1;
    }

    let result: int = target.lastIndexOf(50 as {{.item.primitiveType}}, 3);

    if(result == 3) return 0;
    return 1;
}



function testMap(): int {
    let source: {{.item.primitiveType}}[] = [10 as {{.item.primitiveType}}, 20 as {{.item.primitiveType}}, 30 as {{.item.primitiveType}},
                                            40 as {{.item.primitiveType}}, 50 as {{.item.primitiveType}}, 60 as {{.item.primitiveType}}];
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});

    let origin: {{.item.objectType}};

    try {
        origin = new {{.item.objectType}}(ss);
        origin.set(source);
    } catch(e) {
        console.println(e);
        return 1;
    }


    let target: {{.item.objectType}};

    try {
        target = origin.map((val: {{.item.primitiveType}}, index: int): {{.item.primitiveType}} => { return -val as {{.item.primitiveType}} });
    } catch(e) {
        console.println(e);
        return 1;
    }


    if(target.length != source.length) {
        console.println("Array length mismatch");
        return 1;
    }

    for(let i:int = 0; i< source.length; i++) {

        if(target.at(i) != -source[i]) {
            console.println("Array data mismatch");
            return 1;
        }
    }
    return 0;
}


function testSlice0(): int {
    let source: {{.item.primitiveType}}[] = [10 as {{.item.primitiveType}}, 20 as {{.item.primitiveType}}, 30 as {{.item.primitiveType}},
                                            40 as {{.item.primitiveType}}, 50 as {{.item.primitiveType}}, 60 as {{.item.primitiveType}}];
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});

    let origin: {{.item.objectType}};

    try {
        origin = new {{.item.objectType}}(ss);
        origin.set(source);
    } catch(e) {
        console.println(e);
        return 1;
    }


    let target: {{.item.objectType}};

    try {
        target = origin.slice();
    } catch(e) {
        console.println(e);
        return 1;
    }

    if(target.length != origin.length) {
        console.println("Array length mismatch on slice");
        return 1;
    }

    //Check all the data copied;
    for(let i:int = 0; i< origin.length; i++) {
        let tv = target.at(i) as {{.item.primitiveType}};
        let ov = origin.at(i) as {{.item.primitiveType}};
        console.println(source[i] + "->" + tv + "->" + ov);
        if(tv != ov) {
            console.println("Array data mismatch");
            return 1;
        }
    }
    return 0;
}

function testSlice1(): int {
    let source: {{.item.primitiveType}}[] = [10 as {{.item.primitiveType}}, 20 as {{.item.primitiveType}}, 30 as {{.item.primitiveType}},
                                            40 as {{.item.primitiveType}}, 50 as {{.item.primitiveType}}, 60 as {{.item.primitiveType}}];
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});

    let origin: {{.item.objectType}};

    try {
        origin = new {{.item.objectType}}(ss);
        origin.set(source);
    } catch(e) {
        console.println(e);
        return 1;
    }

    let sliceStart: int = 1;
    let sliceEnd: int = origin.length;

    let target: {{.item.objectType}};

    try {
        target = origin.slice(sliceStart);
    } catch(e) {
        console.println(e);
        return 1;
    }

    if(target.length != origin.length - sliceStart) {
        console.println("Array length mismatch on slice1");
        return 1;
    }

    //Check all the data copied;
    for(let i:int = sliceStart; i< sliceEnd; i++) {
        let tv = target.at(i - sliceStart) as {{.item.primitiveType}};
        let ov = origin.at(i) as {{.item.primitiveType}};
        console.println(source[i] + "->" + tv + "->" + ov);
        if(tv != ov) {
            console.println("Array data mismatch");
            return 1;
        }
    }
    return 0;
}

function testSlice2(): int {
    let source: {{.item.primitiveType}}[] = [10 as {{.item.primitiveType}}, 20 as {{.item.primitiveType}}, 30 as {{.item.primitiveType}},
                                            40 as {{.item.primitiveType}}, 50 as {{.item.primitiveType}}, 60 as {{.item.primitiveType}}];
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});

    let origin: {{.item.objectType}};

    try {
        origin = new {{.item.objectType}}(ss);
        origin.set(source);
    } catch(e) {
        console.println(e);
        return 1;
    }

    let sliceStart: int = 1;
    let sliceEnd: int = 4;

    let target: {{.item.objectType}};

    try {
        target = origin.slice(sliceStart, sliceEnd);
    } catch(e) {
        console.println(e);
        return 1;
    }

    if(target.length != sliceEnd - sliceStart) {
        console.println("Array length mismatch on slice2");
        return 1;
    }

    //Check all the data copied;
    for(let i:int = sliceStart; i< sliceEnd; i++) {
        let tv = target.at(i - sliceStart) as {{.item.primitiveType}};
        let ov = origin.at(i) as {{.item.primitiveType}};
        console.println(source[i] + "->" + tv + "->" + ov);
        if(tv != ov) {
            console.println("Array data mismatch");
            return 1;
        }
    }
    return 0;
}

function createFromSource(source: {{.item.primitiveType}}[]): {{.item.objectType}} throws {
    let ss = new ArrayBuffer(source.length * {{.item.primitiveSizeBytes}});

    let origin: {{.item.objectType}};

    try {
        origin = new {{.item.objectType}}(ss);
        origin.set(source);
    } catch(e) {
        console.println(e);
        throw new Exception("Unable to create typed array");
    }

    return origin;
}

function dump(source: {{.item.primitiveType}}[], origin: {{.item.objectType}}): void {
    console.print("Expected: ");
    for(let i: int = 0; i< source.length; i++) console.print(source[i] + " ");
    console.println();
    console.print("Passed: ");
    for(let i: int = 0; i< origin.length; i++) console.print(origin.at(i) + " ");
    console.println();
}


function assertArrayEqualsToTypedArray(source: {{.item.primitiveType}}[], origin: {{.item.objectType}}): int {
    if(origin.length != source.length) {
        return 1;
    }

    for(let i: int; i< origin.length; i++) {
        let tv: {{.item.primitiveType}} = origin.at(i) as {{.item.primitiveType}};
        let sv: {{.item.primitiveType}} = source[i] as {{.item.primitiveType}};
        if(tv != sv) {
            console.println("Unexpected data changed at [" + i + "] " + sv + "->" + tv);

            return 1;
        }
    }

    return 0;
}

const src: {{.item.primitiveType}}[] = [1 as {{.item.primitiveType}},
                                        2 as {{.item.primitiveType}},
                                        3 as {{.item.primitiveType}},
                                        4 as {{.item.primitiveType}},
                                        5 as {{.item.primitiveType}}];

{%+ for ci in copyWithin %}
function {{.ci.name}}(): int {
    let origin: {{.item.objectType}};

    let expected: {{.item.primitiveType}}[] = [{%+ for ea in ci.expected %}{{.ea}} as {{.item.primitiveType}}, {% endfor %}];

    try {
        origin = createFromSource(src);
    } catch(e) {
        console.println(e);
        return 1;
    }
    origin.copyWithin({{.ci.params|join(', ')}});
    return assertArrayEqualsToTypedArray(expected, origin);
}
{%+ endfor %}
